package com.tanjie.blackbean.system.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.fasterxml.jackson.annotation.JsonValue;
import com.tanjie.blackbean.cache.repositories.UserInfoCache;
import com.tanjie.blackbean.cache.repositories.UserInfoCacheRepository;
import com.tanjie.blackbean.mybatis.query.Condition;
import com.tanjie.blackbean.pojo.auth.UserInfo;
import com.tanjie.blackbean.pojo.system.ClientType;
import com.tanjie.blackbean.system.admin.dao.MenuDaoService;
import com.tanjie.blackbean.system.admin.dao.MenuMapper;
import com.tanjie.blackbean.system.admin.dao.RoleMenuMapper;
import com.tanjie.blackbean.system.admin.model.menu.*;
import com.tanjie.blackbean.system.admin.model.rel.RoleMenuPO;
import com.tanjie.blackbean.system.admin.service.IMenuService;
import com.tanjie.blackbean.pojo.exception.AuthException;
import com.tanjie.blackbean.pojo.exception.BizException;
import com.tanjie.blackbean.pojo.response.AuthErrorCode;
import com.tanjie.blackbean.pojo.response.BizErrorCode;
import com.tanjie.blackbean.core.web.BlackbeanRequestContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static com.tanjie.blackbean.pojo.system.SystemConstant.TREE_ROOT_ID;

/**
 * 菜单管理
 *
 * @author tanjie
 * @since 2022-04-20
 */
@Slf4j
@Service
public class MenuServiceImpl implements IMenuService {

    @Resource
    MenuMapper menuMapper;

    @Resource
    MenuDaoService menuDaoService;

    @Resource
    UserInfoCacheRepository userInfoCacheRepository;

    @Resource
    RoleMenuMapper roleMenuMapper;

    @JsonValue
    @Override

    public void create(MenuDTO dto) {
        // 校验菜单名称是否有重复
        String name = dto.getName();
        // 创建新增对象
        MenuPO insertPo = BeanUtil.copyProperties(dto, MenuPO.class);
        // 校验父级id并设置值
        String parentId = dto.getParentId();
        log.info("传入的parentId={}", parentId);
        if (TREE_ROOT_ID.equals(parentId)) {
            // 一个client-type只能有一个根节点
            this.verifyClientTypeRootMenuExist(dto.getClientType());
            log.info("设置为根节点，层级layer=1");
            insertPo.setParentId(TREE_ROOT_ID);
            insertPo.setLayer(1);
        } else {
            MenuPO parentExistOne = menuMapper.selectById(parentId);
            if (parentExistOne == null) {
                throw new BizException("传入的parentId:{}在系统中不存在", parentId);
            }
            int menuLayer = parentExistOne.getLayer() + 1;
            log.info("设置为{}的子级，层级layer={}", parentExistOne.getName(), menuLayer);
            insertPo.setLayer(menuLayer);
            insertPo.setClientType(parentExistOne.getClientType());
        }
        this.verifyMenuNameRepeat(name, insertPo.getClientType(), null);
        insertPo.setId(null);
        menuMapper.insert(insertPo);
    }


    @Override
    public void update(MenuDTO dto) {
        String id = dto.getId();
        MenuPO existOne = getByIdThr(id);

        String modifyName = dto.getName();
        String existName = existOne.getName();
        // 校验菜单路由名称是否重复
        if (!modifyName.equals(existName)) {
            ClientType modifyClientType = dto.getClientType();
            ClientType existOneClientType = existOne.getClientType();
            if (!existOneClientType.equals(modifyClientType)) {
                existOneClientType = modifyClientType;
            }
            this.verifyMenuNameRepeat(modifyName, existOneClientType, id);
        }
        // 创建更新对象
        MenuPO updateMenuPo = BeanUtil.copyProperties(dto, MenuPO.class);
        // 更换父级
        String existParentId = existOne.getParentId();
        String modifyParentId = dto.getParentId();
        if (!existParentId.equals(modifyParentId)) {
            // 迁移到了根级
            if (TREE_ROOT_ID.equals(modifyParentId)) {
                this.verifyClientTypeRootMenuExist(existOne.getClientType());
                updateMenuPo.setLayer(1);
                this.recursionUpdate(id, 1);
            } else {
                MenuPO parentOne = menuMapper.selectById(modifyParentId);
                if (parentOne == null) {
                    throw new BizException("更换的父级id:{}在系统中不存在", modifyParentId);
                }
                Integer newParentLayer = parentOne.getLayer();
                updateMenuPo.setLayer(newParentLayer + 1);
                this.recursionUpdate(id, newParentLayer + 1);
            }
        }
        updateMenuPo.setClientType(null);
        menuMapper.updateById(updateMenuPo);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String id) {
        if (StrUtil.isBlank(id)) {
            throw new BizException(BizErrorCode.ID_REQUIRE_NOT_BLANK);
        }
        MenuPO existOne = menuMapper.selectById(id);
        if (existOne == null) {
            throw new BizException(BizErrorCode.ID_NOT_EXIST);
        }
        menuMapper.deleteById(id);
        recursiveDelete(id);
    }

    @Override
    public MenuVO detail(String id) {
        return BeanUtil.copyProperties(getByIdThr(id), MenuVO.class);
    }

    private MenuPO getByIdThr(String id) {
        if (StrUtil.isBlank(id)) {
            throw new BizException(BizErrorCode.ID_REQUIRE_NOT_BLANK);
        }
        MenuPO existOne = menuMapper.selectById(id);
        if (existOne == null) {
            throw new BizException(BizErrorCode.ID_NOT_EXIST);
        }
        return existOne;
    }

    @Override
    public List<MenuVO> queryList(MenuQuery dto) {
        QueryWrapper<MenuPO> queryWrapper = getQueryWrapper(dto);
        List<MenuPO> list = menuMapper.selectList(queryWrapper);
        return BeanUtil.copyToList(list, MenuVO.class);
    }

    @Override
    public List<Tree<String>> tree() {
        List<MenuPO> list = new LambdaQueryChainWrapper<>(menuMapper)
                .orderBy(true, false, MenuPO::getParentId)
                .orderBy(true, false, MenuPO::getSort).list();
        return this.convertToTree(list);
    }

    @Override
    public List<Tree<String>> getRoute() {
        UserInfoCache userInfoCache = userInfoCacheRepository.findById(BlackbeanRequestContextHolder.getUserIdThr()).orElse(null);
        if (userInfoCache == null) {
            throw new AuthException(AuthErrorCode.CANNOT_GET_USERINFO_FROM_CACHE);
        }
        List<UserInfo.Role> roleList = userInfoCache.getRoles();
        if (CollectionUtil.isEmpty(roleList)) {
            return new ArrayList<>();
        }
        ClientType clientType = userInfoCache.getClientType();
        List<String> roleIdList = roleList.stream().map(UserInfo.Role::getId).collect(Collectors.toList());
        List<RouteVO> routeList = menuMapper.listRoute(roleIdList, clientType.getCode());
        return this.recursionRoute(routeList);
    }

    @Override
    public List<String> roleBoundMenuList(String roleId) {
        return new LambdaQueryChainWrapper<>(roleMenuMapper)
                .eq(RoleMenuPO::getRoleId, roleId)
                .list()
                .stream()
                .map(RoleMenuPO::getMenuId)
                .collect(Collectors.toList());
    }

    private QueryWrapper<MenuPO> getQueryWrapper(MenuQuery dto) {
        QueryWrapper<MenuPO> queryWrapper = null;
        if (dto != null) {
            queryWrapper = Condition.getQueryWrapper(BeanUtil.copyProperties(dto, MenuPO.class));
        }
        return queryWrapper;
    }


    private void verifyClientTypeRootMenuExist(ClientType clientType) {
        if (new LambdaQueryChainWrapper<>(menuMapper)
                .eq(MenuPO::getClientType, clientType.getCode())
                .eq(MenuPO::getParentId, TREE_ROOT_ID)
                .count() > 0) {
            throw new BizException("客户端类型:{}已存在一个菜单根节点", clientType.getDesc());
        }
    }


    /**
     * 校验菜单路由名称是否重复
     *
     * @param name       菜单路由名称
     * @param clientType 客户端类型
     * @param existId    已存在的菜单id（更新时）
     */
    private void verifyMenuNameRepeat(String name, ClientType clientType, String existId) {
        Long existNameCount = new LambdaQueryChainWrapper<>(menuMapper)
                .ne(StrUtil.isNotBlank(existId), MenuPO::getId, existId)
                .eq(MenuPO::getClientType, clientType.getCode())
                .eq(MenuPO::getName, name)
                .count();
        if (existNameCount > 0) {
            throw new BizException("菜单路由名称:{}，在{}中已存在", name, clientType.getDesc());
        }
    }

    private List<Tree<String>> convertToTree(List<MenuPO> allList) {
        return TreeUtil.build(allList, TREE_ROOT_ID, ((po, treeNode) -> {
            treeNode.setId(po.getId());
            treeNode.setParentId(po.getParentId());
            treeNode.setName(po.getName());
            treeNode.setWeight(po.getSort());
            treeNode.putExtra("title", po.getTitle());
            treeNode.putExtra("icon", po.getIcon());
            treeNode.putExtra("path", po.getPath());
            treeNode.putExtra("visible", po.getVisible());
            treeNode.putExtra("layer", po.getLayer());
            treeNode.putExtra("clientType", po.getClientType());
        }));
    }

    /**
     * 递归更新所有子级层级layer
     *
     * @param parentId 父级id
     * @param layer    父级layer
     */
    private void recursionUpdate(String parentId, Integer layer) {
        List<MenuPO> children = new LambdaQueryChainWrapper<>(menuMapper).eq(MenuPO::getParentId, parentId).list();
        if (CollectionUtil.isEmpty(children)) {
            return;
        }
        for (MenuPO child : children) {
            String id = child.getId();
            new LambdaUpdateChainWrapper<>(menuMapper)
                    .eq(MenuPO::getId, id)
                    .set(MenuPO::getLayer, layer + 1)
                    .set(MenuPO::getUpdateTime, new Date())
                    .update();
            this.recursionUpdate(id, layer + 1);
        }
    }

    /**
     * 递归删除所有子级
     *
     * @param id 菜单id
     */
    private void recursiveDelete(String id) {
        List<MenuPO> list = new LambdaQueryChainWrapper<>(menuMapper).eq(MenuPO::getParentId, id).list();
        if (CollectionUtil.isNotEmpty(list)) {
            for (MenuPO menu : list) {
                String subId = menu.getId();
                menuMapper.deleteById(subId);
                recursiveDelete(subId);
            }
        }
    }

    private List<Tree<String>> recursionRoute(List<RouteVO> menuList) {
        return TreeUtil.build(menuList, TREE_ROOT_ID, (menuRole, treeNode) -> {
            treeNode.setId(menuRole.getId());
            treeNode.setParentId(menuRole.getParentId());
            treeNode.setName(menuRole.getName());
            treeNode.setWeight(menuRole.getSort());
            treeNode.putExtra("path", menuRole.getPath());
            treeNode.putExtra("visible", menuRole.getVisible());
            treeNode.putExtra("layer", menuRole.getLayer());
            Map<String, Object> metaMap = new HashMap<>(16);
            metaMap.put("title", menuRole.getTitle());
            metaMap.put("icon", menuRole.getIcon());
            metaMap.put("roles", menuRole.getRoles());
            treeNode.putExtra("meta", metaMap);
        });
    }

}
